package com.fafian.gdc.socket;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.Socket;

import com.fafian.gdc.core.Command;
import com.fafian.gdc.socket.SocketMessage.MessageType;
import com.fafian.gdc.socket.SocketMessageResponse.Result;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;

public class GDCSocket  {

	JsonWriter writer;
	JsonReader reader;
	Socket socket;
	
	public GDCSocket(Socket socket) throws GDCSocketException{
		this.socket = socket;
		try{
			initStreams();
		}
		catch (Exception e){
			throw new GDCSocketException ("Unable to init socket", e);
		}
		
	}
	
	public GDCSocket(String ip, int port) throws GDCSocketException{
		try{
			socket = new Socket(ip, port);
			initStreams();
		}catch (Exception e){
			throw new GDCSocketException ("Unable to init socket", e);
		}
		
	}
	
	private void initStreams() throws UnsupportedEncodingException, IOException{
		reader = new JsonReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
		writer = new JsonWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));
	}
	
	public void close() throws GDCSocketException{
		try{
			reader.close();
			writer.close();
			socket.close();
		}catch (Exception e){
			throw new GDCSocketException ("Unable to close streams and socket", e);
		}
	}
	
	protected SocketMessage getIncomingMessage() throws IOException{
		reader.beginObject();
		Command command = Command.NOP;
		Result result = Result.KO;
		String receivedApiKey = "", explanation="";
		MessageType tipo = MessageType.UNKNOWN;
		while (reader.hasNext()){
			boolean skip = true;
			String name = reader.nextName();
			if( "apiKey".equals(name)){
				skip = false;
				receivedApiKey = reader.nextString();
				if (tipo != MessageType.REQUEST)
					tipo = MessageType.REQUEST;
			}
			if ("command".equals(name)){
				skip = false;
				if (tipo != MessageType.REQUEST)
					tipo = MessageType.REQUEST;
				try{
					command = Command.valueOf(reader.nextString());
				}
				catch (Exception e){
					e.printStackTrace();
				}
			}
			if("result".equals(name)){
				skip = false;
				if (tipo != MessageType.RESPONSE)
					tipo = MessageType.RESPONSE;
				try {
					String resultString = reader.nextString();
					result = Result.valueOf(resultString);
				}
				catch (Exception e){
					e.printStackTrace();
				}
			}
			if( "explanation".equals(name)){
				skip = false;
				explanation = reader.nextString();
				if (tipo != MessageType.RESPONSE)
					tipo = MessageType.RESPONSE;
			}
			if (skip){
				reader.skipValue();
			}
			
		}
		reader.endObject();
		switch (tipo){
		case REQUEST:
			return new SocketMessageRequest(receivedApiKey, command);

		case RESPONSE:
			return new SocketMessageResponse(result, explanation);
	
		default:
			return new SocketMessage(tipo);
		}
		
	}

	
	public SocketMessageResponse sendRequest(String apiKey, Command command) throws GDCSocketException{
		try{
			sendMessage(new SocketMessageRequest(apiKey, command));
			SocketMessage response = getIncomingMessage();
			switch (response.getType()){
			case REQUEST:
				throw new GDCSocketException("Error while sending request, expected type was RESPONSE not REQUEST ");
			case RESPONSE:
				return (SocketMessageResponse) response;
			case UNKNOWN:
			default:
				throw new GDCSocketException("Error while sending request, expected type was RESPONSE not UNKNOWN");
			
			}
		}
		catch (IOException e){
			throw new GDCSocketException("Error while sending request", e);
		}
		
	}
	
	protected void sendMessage(SocketMessage message) throws IOException{
		switch(message.getType()){
		case REQUEST:
			writer.beginObject();
			writer.name("apiKey").value(((SocketMessageRequest) message).getApiKey());
			writer.name("command").value(((SocketMessageRequest) message).getCommand().name());
			writer.endObject();
			writer.flush();
			break;
		case RESPONSE:
			writer.beginObject();
			writer.name("result").value(((SocketMessageResponse) message).getResult().name());
			writer.name("explanation").value(((SocketMessageResponse) message).getExplanation());
			writer.endObject();			
			writer.flush();
			break;
		case UNKNOWN:
			break;
		default:
			break;
		
		}
	}
}
